import { hc } from "../../hc";
import CodoBase from "../../../CodoBase";
import { Application } from "egg";

/** 缓存命令 存取队列 */
const getCacheListCmdKey = (key: hc.wan.deviceId) => `codo:hc:wan:cmd:${key}`;

/** 封装基础的命令服务类，封装通用方法，其他服务继承 */
export default class HcWanBaseCmdService extends CodoBase {
	constructor(protected device: hc.http.device, protected app: Application) {
		super(app);
	}
	/** 命令响应结果存储key */
	private getCacheCmdResultKey(taskId: string) {
		return `codo:hc:wan:cmd:result:${this.device.deviceId}:${taskId}`;
	}
	/** 保存缓存命令 到持久化中 */
	async saveCacheCMD(operator: string, info?: Record<string, any>, isFirst?: boolean) {
		const deviceId = this.device.deviceId;
		const key = getCacheListCmdKey(deviceId);
		info = info || {};
		const content: hc.wan.CMDObject = {
			deviceId,
			operator,
			taskId: Number((Math.random() * 1000000 + Math.random() * 100000).toFixed(0)),
			info,
		};
		if (isFirst) {
			await this.app.redis.lpush(key, JSON.stringify(content));
		} else {
			await this.app.redis.rpush(key, JSON.stringify(content));
		}
		return content;
	}

	/** 保存优先的缓存命令 */
	async saveFirstCacheCMD(operator: string, info?: { [key: string]: any }) {
		return await this.saveCacheCMD(operator, info, true);
	}

	/** 获取缓存的命令 仅1个 */
	async getCacheCMD() {
		const r = await this.app.redis.lpop(getCacheListCmdKey(this.device.deviceId));
		if (!r) return null;
		try {
			const obj = JSON.parse(r);
			return obj as hc.wan.CMDObject;
		} catch (error) {
			this.app.logger.error(`parse redis cache cmd error: ${r}`);
			return null;
		}
	}

	/** 创建命令对象，响应给设备的 */
	createCMDObject(row: hc.wan.CMDObject) {
		const cmdInfo = row.info || {};

		const body: hc.wan.cmdResponse<{}> = {
			TimeStamp: new Date().getTime(),
			TaskId: row.taskId,
			OperatorInfo: {
				operator: row.operator,
				DeviceID: row.deviceId,
				...cmdInfo,
			},
		};

		// 特殊情况处理，设置设备时间时，由于轮询有间隔。所以时间需要重置为当前时间
		if (row.operator === "SetSysTime") {
			// 如果不是外部手动设置时间，则自动更正为当前服务器时间
			if (!cmdInfo.isOutDate) {
				const df = new Date();
				// @ts-ignore
				body.OperatorInfo.info = {
					Year: df.getFullYear(),
					Month: df.getMonth() + 1,
					Day: df.getDate(),
					Hour: df.getHours(),
					Minute: df.getMinutes(),
					Second: df.getSeconds(),
				};
			}
			// @ts-ignore
			// delete body.OperatorInfo.isOutDate;
		}

		return body;
	}

	/** 手动自行下发任意缓存命令 ，并等待响应结果 */
	async execCMD<T extends {}>(
		operator: string,
		info?: { [key: string]: any },
		isFirst?: boolean
	) {
		const cacheRow = await this.saveCacheCMD(operator, info, isFirst);
		const cacheResult = await this.getCMDResultFromRedis(cacheRow.taskId.toString());

		const r = this.handleCMDResult<T>(cacheResult);
		if (!r) throw new Error("命令无响应");
		return r;
	}

	/** 设备命令结果存入redis */
	async cmdResultSetRedis(taskId: string, data: Record<string, any>) {
		await this.app.redis.set(this.getCacheCmdResultKey(taskId), JSON.stringify(data));
	}

	/** 轮询从redis中取出命令结果 */
	async getCMDResultFromRedis(taskId: string, timeout = 30) {
		let times = 0;

		const redisKey = this.getCacheCmdResultKey(taskId);

		const doit = async (): Promise<string | null> => {
			const res = await this.app.redis.get(redisKey);
			if (!res) {
				times++;
				if (times > timeout) return null;
				await this.sleep(1000);
				return await doit();
			}
			return res;
		};
		await this.sleep(1000);
		const res = await doit();
		if (res) {
			this.app.redis.del(redisKey);
			return JSON.parse(res) as hc.wan.heartbeatBody["data"];
		}
		return null;
	}

	/** 处理命令结果
	 * - 如果命令执行成功，删除db中的缓存命令
	 * - 如果执行失败，记录失败信息到db，不删
	 * - 返回对象，success ，data为成功数据或失败信息
	 */
	private handleCMDResult<T extends {}>(data: hc.wan.heartbeatBody["data"] | null) {
		if (!data) return data;
		const isSuccess = data.OperatorInfo.SuccessCount > 0 && data.OperatorInfo.ErrCount === 0;
		if (isSuccess) {
			return {
				success: true,
				data: data.OperatorInfo.SuccessInfo as T[],
			};
		} else {
			return {
				success: false,
				data: data.OperatorInfo.errorInfo,
			};
		}
	}

	async sleep(ms: number) {
		return new Promise((resolve) => {
			setTimeout(resolve, ms);
		});
	}
}
